namespace System;
/// <summary>
/// ObjectHelper
/// </summary>
public static class ObjectHelper
{
    private static readonly ConcurrentDictionary<string, PropertyInfo?> CachedObjectProperties =
        new ConcurrentDictionary<string, PropertyInfo?>();
    /// <summary>
    /// 
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="propertySelector"></param>
    /// <param name="valueFactory"></param>
    /// <param name="ignoreAttributeTypes"></param>
    /// <typeparam name="TObject"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public static void TrySetProperty<TObject, TValue>(
        TObject obj,
        Expression<Func<TObject, TValue>> propertySelector,
        Func<TValue> valueFactory,
        params Type[] ignoreAttributeTypes)
    {
        TrySetProperty(obj, propertySelector, x => valueFactory(), ignoreAttributeTypes);
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="propertySelector"></param>
    /// <param name="valueFactory"></param>
    /// <param name="ignoreAttributeTypes"></param>
    /// <typeparam name="TObject"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public static void TrySetProperty<TObject, TValue>(
        TObject obj,
        Expression<Func<TObject, TValue>> propertySelector,
        Func<TObject, TValue> valueFactory,
        params Type[]? ignoreAttributeTypes)
    {
        var cacheKey = $"{obj?.GetType().FullName}-" +
                       $"{propertySelector}-" +
                       $"{(ignoreAttributeTypes is not null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}";

        var property = CachedObjectProperties.GetOrAdd(cacheKey, (_) =>
        {
            if (propertySelector.Body.NodeType != ExpressionType.MemberAccess)
            {
                return null;
            }

            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression is null) return null;

            var propertyInfo = obj?.GetType().GetProperties().FirstOrDefault(x =>
                x.Name == memberExpression.Member.Name &&
                x.GetSetMethod(true) is not null);

            if (propertyInfo is null)
            {
                return null;
            }

            if (ignoreAttributeTypes is not null &&
                ignoreAttributeTypes.Any(ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true)))
            {
                return null;
            }

            return propertyInfo;
        });

        property?.SetValue(obj, valueFactory(obj));
    }

}